/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    lpcreply.c

Abstract:

    Local Inter-Process Communication (LPC) reply system services.

Author:

    Steve Wood (stevewo) 15-May-1989

Revision History:

--*/

#include "lpcp.h"

NTSTATUS
LpcpCopyRequestData(
    IN BOOLEAN WriteToMessageData,
    IN HANDLE PortHandle,
    IN PPORT_MESSAGE Message,
    IN ULONG DataEntryIndex,
    IN PVOID Buffer,
    IN ULONG BufferSize,
    OUT PULONG NumberOfBytesCopied OPTIONAL
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,NtReplyPort)
#pragma alloc_text(PAGE,NtReplyWaitReplyPort)
#pragma alloc_text(PAGE,NtReadRequestData)
#pragma alloc_text(PAGE,NtWriteRequestData)
#pragma alloc_text(PAGE,LpcpCopyRequestData)
#endif


NTSTATUS
NtReplyPort(
    IN HANDLE PortHandle,
    IN PPORT_MESSAGE ReplyMessage
    )
{
    KPROCESSOR_MODE PreviousMode;
    PLPCP_PORT_OBJECT PortObject;
    PORT_MESSAGE CapturedReplyMessage;
    NTSTATUS Status;
    PLPCP_MESSAGE Msg;
    PETHREAD CurrentThread;
    PETHREAD WakeupThread;

    PAGED_CODE();
    CurrentThread = PsGetCurrentThread();

    //
    // Get previous processor mode and probe output arguments if necessary.
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {
            ProbeForRead( ReplyMessage,
                          sizeof( *ReplyMessage ),
                          sizeof( ULONG )
                        );
            CapturedReplyMessage = *ReplyMessage;
            }
        except( EXCEPTION_EXECUTE_HANDLER ) {
            return( GetExceptionCode() );
            }
        }
    else {
        CapturedReplyMessage = *ReplyMessage;
        }

    //
    // Reference the port object by handle
    //

    Status = LpcpReferencePortObject( PortHandle,
                                      0,
                                      PreviousMode,
                                      &PortObject
                                    );
    if (!NT_SUCCESS( Status )) {
        return( Status );
        }

    //
    // Translate the ClientId from the connection request into a
    // thread pointer.  This is a referenced pointer to keep the thread
    // from evaporating out from under us.
    //

    Status = PsLookupProcessThreadByCid( &CapturedReplyMessage.ClientId,
                                         NULL,
                                         &WakeupThread
                                       );
    if (!NT_SUCCESS( Status )) {
        ObDereferenceObject( PortObject );
        return( Status );
        }

    //
    // Acquire the mutex that gaurds the LpcReplyMessage field of
    // the thread and get the pointer to the message that the thread
    // is waiting for a reply to.
    //

    ExAcquireFastMutex( &LpcpLock );
    Msg = (PLPCP_MESSAGE)LpcpAllocateFromPortZone( CapturedReplyMessage.u1.s1.TotalLength );
    if (Msg == NULL) {
        ExReleaseFastMutex( &LpcpLock );
        ObDereferenceObject( WakeupThread );
        ObDereferenceObject( PortObject );
        return( STATUS_NO_MEMORY );
        }

    //
    // See if the thread is waiting for a reply to the message
    // specified on this call.  If not then a bogus message
    // has been specified, so release the mutex, dereference the thread
    // and return failure.
    //

    if (WakeupThread->LpcReplyMessageId != CapturedReplyMessage.MessageId) {
        LpcpPrint(( "%s Attempted reply to Thread %lx (%s)\n",
                    PsGetCurrentProcess()->ImageFileName,
                    WakeupThread,
                    THREAD_TO_PROCESS( WakeupThread )->ImageFileName
                 ));
        LpcpPrint(( "failed.  MessageId == %u  Client Id: %x.%x\n",
                    CapturedReplyMessage.MessageId,
                    CapturedReplyMessage.ClientId.UniqueProcess,
                    CapturedReplyMessage.ClientId.UniqueThread
                 ));
        LpcpPrint(( "         Thread MessageId == %u  Client Id: %x.%x\n",
                    WakeupThread->LpcReplyMessageId,
                    WakeupThread->Cid.UniqueProcess,
                    WakeupThread->Cid.UniqueThread
                 ));
#if DBG
        if (LpcpStopOnReplyMismatch) {
            DbgBreakPoint();
            }
#endif
        LpcpFreeToPortZone( Msg, TRUE );
        ExReleaseFastMutex( &LpcpLock );
        ObDereferenceObject( WakeupThread );
        ObDereferenceObject( PortObject );
        return( STATUS_REPLY_MESSAGE_MISMATCH );
        }

    LpcpTrace(( "%s Sending Reply Msg %lx (%u, %x) [%08x %08x %08x %08x] to Thread %lx (%s)\n",
                PsGetCurrentProcess()->ImageFileName,
                Msg,
                CapturedReplyMessage.MessageId,
                CapturedReplyMessage.u2.s2.DataInfoOffset,
                *((PULONG)(Msg+1)+0),
                *((PULONG)(Msg+1)+1),
                *((PULONG)(Msg+1)+2),
                *((PULONG)(Msg+1)+3),
                WakeupThread,
                THREAD_TO_PROCESS( WakeupThread )->ImageFileName
             ));

    if (CapturedReplyMessage.u2.s2.DataInfoOffset != 0) {
        LpcpFreeDataInfoMessage( PortObject,
                                 CapturedReplyMessage.MessageId,
                                 CapturedReplyMessage.CallbackId
                               );
        }

    //
    // Release the mutex that guards the LpcReplyMessage field
    // after marking message as being replied to.
    //

    Msg->RepliedToThread = WakeupThread;
    WakeupThread->LpcReplyMessageId = 0;
    WakeupThread->LpcReplyMessage = (PVOID)Msg;

    //
    // Remove the thread from the reply rundown list as we are sending the reply.
    //
    if (!WakeupThread->LpcExitThreadCalled && !IsListEmpty( &WakeupThread->LpcReplyChain )) {
        RemoveEntryList( &WakeupThread->LpcReplyChain );
        InitializeListHead( &WakeupThread->LpcReplyChain );
        }

    if (CurrentThread->LpcReceivedMsgIdValid &&
        CurrentThread->LpcReceivedMessageId == CapturedReplyMessage.MessageId
       ) {
        CurrentThread->LpcReceivedMessageId = 0;
        CurrentThread->LpcReceivedMsgIdValid = FALSE;
        }
    ExReleaseFastMutex( &LpcpLock );

    //
    // Copy the reply message to the request message buffer
    //
    try {
        LpcpMoveMessage( &Msg->Request,
                         &CapturedReplyMessage,
                         (ReplyMessage + 1),
                         LPC_REPLY,
                         NULL
                       );
        }
    except( EXCEPTION_EXECUTE_HANDLER ) {
        Status = GetExceptionCode();
        }

    //
    // Wake up the thread that is waiting for an answer to its request
    // inside of NtRequestWaitReplyPort or NtReplyWaitReplyPort.  That
    // will dereference itself when it wakes up.
    //

    KeReleaseSemaphore( &WakeupThread->LpcReplySemaphore,
                        0,
                        1L,
                        FALSE
                      );

    //
    // Dereference port object and return the system service status.
    //

    ObDereferenceObject( PortObject );
    return( Status );
}


NTSTATUS
NtReplyWaitReplyPort(
    IN HANDLE PortHandle,
    IN OUT PPORT_MESSAGE ReplyMessage
    )
{
    KPROCESSOR_MODE PreviousMode;
    NTSTATUS Status;
    PLPCP_PORT_OBJECT PortObject;
    PORT_MESSAGE CapturedReplyMessage;
    PLPCP_MESSAGE Msg;
    PETHREAD CurrentThread;
    PETHREAD WakeupThread;

    PAGED_CODE();
    CurrentThread = PsGetCurrentThread();

    //
    // Get previous processor mode and probe output arguments if necessary.
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {
            ProbeForRead( ReplyMessage,
                          sizeof( *ReplyMessage ),
                          sizeof( ULONG )
                        );
            CapturedReplyMessage = *ReplyMessage;
            }
        except( EXCEPTION_EXECUTE_HANDLER ) {
            return( GetExceptionCode() );
            }
        }
    else {
        CapturedReplyMessage = *ReplyMessage;
        }

    //
    // Reference the communication port object by handle.  Return status if
    // unsuccessful.
    //

    Status = LpcpReferencePortObject( PortHandle,
                                      0,
                                      PreviousMode,
                                      &PortObject
                                    );
    if (!NT_SUCCESS( Status )) {
        return( Status );
        }


    //
    // Translate the ClientId from the connection request into a
    // thread pointer.  This is a referenced pointer to keep the thread
    // from evaporating out from under us.
    //

    Status = PsLookupProcessThreadByCid( &CapturedReplyMessage.ClientId,
                                         NULL,
                                         &WakeupThread
                                       );
    if (!NT_SUCCESS( Status )) {
        ObDereferenceObject( PortObject );
        return( Status );
        }

    //
    // Acquire the mutex that gaurds the LpcReplyMessage field of
    // the thread and get the pointer to the message that the thread
    // is waiting for a reply to.
    //

    ExAcquireFastMutex( &LpcpLock );
    Msg = (PLPCP_MESSAGE)LpcpAllocateFromPortZone( CapturedReplyMessage.u1.s1.TotalLength );
    if (Msg == NULL) {
        ExReleaseFastMutex( &LpcpLock );
        ObDereferenceObject( WakeupThread );
        ObDereferenceObject( PortObject );
        return( STATUS_NO_MEMORY );
        }

    //
    // See if the thread is waiting for a reply to the message
    // specified on this call.  If not then a bogus message
    // has been specified, so release the mutex, dereference the thread
    // and return failure.
    //

    if (WakeupThread->LpcReplyMessageId != CapturedReplyMessage.MessageId) {
        LpcpPrint(( "%s Attempted reply wait reply to Thread %lx (%s)\n",
                    PsGetCurrentProcess()->ImageFileName,
                    WakeupThread,
                    THREAD_TO_PROCESS( WakeupThread )->ImageFileName
                 ));
        LpcpPrint(( "failed.  MessageId == %u  Client Id: %x.%x\n",
                    CapturedReplyMessage.MessageId,
                    CapturedReplyMessage.ClientId.UniqueProcess,
                    CapturedReplyMessage.ClientId.UniqueThread
                 ));
        LpcpPrint(( "         Thread MessageId == %u  Client Id: %x.%x\n",
                    WakeupThread->LpcReplyMessageId,
                    WakeupThread->Cid.UniqueProcess,
                    WakeupThread->Cid.UniqueThread
                 ));
#if DBG
        if (LpcpStopOnReplyMismatch) {
            DbgBreakPoint();
            }
#endif
        LpcpFreeToPortZone( Msg, TRUE );
        ExReleaseFastMutex( &LpcpLock );
        ObDereferenceObject( WakeupThread );
        ObDereferenceObject( PortObject );
        return( STATUS_REPLY_MESSAGE_MISMATCH );
        }


    LpcpTrace(( "%s Sending Reply Wait Reply Msg %lx (%u, %x) [%08x %08x %08x %08x] to Thread %lx (%s)\n",
                PsGetCurrentProcess()->ImageFileName,
                Msg,
                CapturedReplyMessage.MessageId,
                CapturedReplyMessage.u2.s2.DataInfoOffset,
                *((PULONG)(Msg+1)+0),
                *((PULONG)(Msg+1)+1),
                *((PULONG)(Msg+1)+2),
                *((PULONG)(Msg+1)+3),
                WakeupThread,
                THREAD_TO_PROCESS( WakeupThread )->ImageFileName
             ));

    if (CapturedReplyMessage.u2.s2.DataInfoOffset != 0) {
        LpcpFreeDataInfoMessage( PortObject,
                                 CapturedReplyMessage.MessageId,
                                 CapturedReplyMessage.CallbackId
                               );
        }

    //
    // Release the mutex that guards the LpcReplyMessage field
    // after marking message as being replied to.
    //

    Msg->RepliedToThread = WakeupThread;
    WakeupThread->LpcReplyMessageId = 0;
    WakeupThread->LpcReplyMessage = (PVOID)Msg;

    //
    // Remove the thread from the reply rundown list as we are sending the reply.
    //
    if (!WakeupThread->LpcExitThreadCalled && !IsListEmpty( &WakeupThread->LpcReplyChain )) {
        RemoveEntryList( &WakeupThread->LpcReplyChain );
        InitializeListHead( &WakeupThread->LpcReplyChain );
        }

    CurrentThread->LpcReplyMessageId = CapturedReplyMessage.MessageId;
    CurrentThread->LpcReplyMessage = NULL;
    if (CurrentThread->LpcReceivedMsgIdValid &&
        CurrentThread->LpcReceivedMessageId == CapturedReplyMessage.MessageId
       ) {
        CurrentThread->LpcReceivedMessageId = 0;
        CurrentThread->LpcReceivedMsgIdValid = FALSE;
        }
    ExReleaseFastMutex( &LpcpLock );

    //
    // Copy the reply message to the request message buffer
    //
    try {
        LpcpMoveMessage( &Msg->Request,
                         &CapturedReplyMessage,
                         (ReplyMessage + 1),
                         LPC_REPLY,
                         NULL
                       );
        }
    except( EXCEPTION_EXECUTE_HANDLER ) {
        Status = GetExceptionCode();
        ObDereferenceObject( WakeupThread );
        ObDereferenceObject( PortObject );
        return Status;
        }

    //
    // Wake up the thread that is waiting for an answer to its request
    // inside of NtRequestWaitReplyPort or NtReplyWaitReplyPort.  That
    // will dereference itself when it wakes up.
    //

    Status = KeReleaseWaitForSemaphore( &WakeupThread->LpcReplySemaphore,
                                        &CurrentThread->LpcReplySemaphore,
                                        Executive,
                                        PreviousMode
                                      );
    if (Status == STATUS_USER_APC) {
        //
        // if the semaphore is signaled, then clear it
        //
        if (KeReadStateSemaphore( &CurrentThread->LpcReplySemaphore )) {
            KeWaitForSingleObject( &CurrentThread->LpcReplySemaphore,
                                   WrExecutive,
                                   KernelMode,
                                   FALSE,
                                   NULL
                                 );
            Status = STATUS_SUCCESS;
            }
        }


    //
    // If the wait succeeded, copy the reply to the reply buffer.
    //

    if (Status == STATUS_SUCCESS ) {

        //
        // Acquire the mutex that gaurds the request message
        // queue.  Remove the request message from the list of
        // messages being processed and free the message back to the queue's zone.
        // If the zone's free list was zero before freeing this message then
        // pulse the free event after free the message so that threads waiting
        // to allocate a request message buffer will wake up.  Finally,
        // release the mutex and return the system service status.
        //

        ExAcquireFastMutex( &LpcpLock );

        Msg = CurrentThread->LpcReplyMessage;
        CurrentThread->LpcReplyMessage = NULL;
#if DBG
        if (Msg != NULL) {
            LpcpTrace(( "%s Got Reply Msg %lx (%u) [%08x %08x %08x %08x] for Thread %lx (%s)\n",
                        PsGetCurrentProcess()->ImageFileName,
                        Msg,
                        Msg->Request.MessageId,
                        *((PULONG)(Msg+1)+0),
                        *((PULONG)(Msg+1)+1),
                        *((PULONG)(Msg+1)+2),
                        *((PULONG)(Msg+1)+3),
                        CurrentThread,
                        THREAD_TO_PROCESS( CurrentThread )->ImageFileName
                     ));
            if (!IsListEmpty( &Msg->Entry )) {
                LpcpTrace(( "Reply Msg %lx has non-empty list entry\n", Msg ));
                }
            }
#endif

        ExReleaseFastMutex( &LpcpLock );

        if (Msg != NULL) {
            try {
                LpcpMoveMessage( ReplyMessage,
                                 &Msg->Request,
                                 (&Msg->Request) + 1,
                                 0,
                                 NULL
                               );
                }
            except( EXCEPTION_EXECUTE_HANDLER ) {
                Status = GetExceptionCode();
                }

            //
            // Acquire the LPC mutex and decrement the reference count for the
            // message.  If the reference count goes to zero the message will be
            // deleted.
            //

            ExAcquireFastMutex( &LpcpLock );

            if (Msg->RepliedToThread != NULL) {
                ObDereferenceObject( Msg->RepliedToThread );
                Msg->RepliedToThread = NULL;
                }

            LpcpFreeToPortZone( Msg, TRUE );

            ExReleaseFastMutex( &LpcpLock );
            }
        }


    ObDereferenceObject( PortObject );
    return( Status );
}


NTSTATUS
NtReadRequestData(
    IN HANDLE PortHandle,
    IN PPORT_MESSAGE Message,
    IN ULONG DataEntryIndex,
    OUT PVOID Buffer,
    IN ULONG BufferSize,
    OUT PULONG NumberOfBytesRead OPTIONAL
    )
{
    PAGED_CODE();

    return LpcpCopyRequestData( FALSE,
                                PortHandle,
                                Message,
                                DataEntryIndex,
                                Buffer,
                                BufferSize,
                                NumberOfBytesRead
                              );
}


NTSTATUS
NtWriteRequestData(
    IN HANDLE PortHandle,
    IN PPORT_MESSAGE Message,
    IN ULONG DataEntryIndex,
    IN PVOID Buffer,
    IN ULONG BufferSize,
    OUT PULONG NumberOfBytesWritten OPTIONAL
    )
{
    PAGED_CODE();
    return LpcpCopyRequestData( TRUE,
                                PortHandle,
                                Message,
                                DataEntryIndex,
                                Buffer,
                                BufferSize,
                                NumberOfBytesWritten
                              );
}


NTSTATUS
LpcpCopyRequestData(
    IN BOOLEAN WriteToMessageData,
    IN HANDLE PortHandle,
    IN PPORT_MESSAGE Message,
    IN ULONG DataEntryIndex,
    IN PVOID Buffer,
    IN ULONG BufferSize,
    OUT PULONG NumberOfBytesCopied OPTIONAL
    )
{
    KPROCESSOR_MODE PreviousMode;
    PLPCP_PORT_OBJECT PortObject;
    PLPCP_MESSAGE Msg;
    PLIST_ENTRY Head, Next;
    NTSTATUS Status;
    PETHREAD ClientThread;
    PPORT_DATA_INFORMATION DataInfo;
    PPORT_DATA_ENTRY DataEntry;
    PORT_MESSAGE CapturedMessage;
    PORT_DATA_INFORMATION CapturedDataInfo;
    PORT_DATA_ENTRY CapturedDataEntry;
    ULONG BytesCopied;

    PAGED_CODE();
    //
    // Get previous processor mode and probe output arguments if necessary.
    //

    PreviousMode = KeGetPreviousMode();
    if (PreviousMode != KernelMode) {
        try {
            if (WriteToMessageData) {
                ProbeForRead( Buffer,
                              BufferSize,
                              1
                            );
                }
            else {
                ProbeForWrite( Buffer,
                               BufferSize,
                               1
                             );
                }

            ProbeForRead( Message,
                          sizeof( *Message ),
                          sizeof( ULONG )
                        );
            CapturedMessage = *Message;
            if (ARGUMENT_PRESENT( NumberOfBytesCopied )) {
                ProbeForWriteUlong( NumberOfBytesCopied );
                }
            }
        except( EXCEPTION_EXECUTE_HANDLER ) {
            return( GetExceptionCode() );
            }
        }
    else {
        CapturedMessage = *Message;
        }

    if (CapturedMessage.u2.s2.DataInfoOffset == 0) {
        return( STATUS_INVALID_PARAMETER );
        }

    //
    // Reference the port object by handle
    //

    Status = LpcpReferencePortObject( PortHandle,
                                      0,
                                      PreviousMode,
                                      &PortObject
                                    );
    if (!NT_SUCCESS( Status )) {
        return( Status );
        }

    //
    // Translate the ClientId from the connection request into a
    // thread pointer.  This is a referenced pointer to keep the thread
    // from evaporating out from under us.
    //

    Status = PsLookupProcessThreadByCid( &CapturedMessage.ClientId,
                                         NULL,
                                         &ClientThread
                                       );
    if (!NT_SUCCESS( Status )) {
        ObDereferenceObject( PortObject );
        return( Status );
        }

    //
    // Acquire the mutex that gaurds the LpcReplyMessage field of
    // the thread and get the pointer to the message that the thread
    // is waiting for a reply to.
    //

    ExAcquireFastMutex( &LpcpLock );

    //
    // See if the thread is waiting for a reply to the message
    // specified on this call.  If not then a bogus message
    // has been specified, so release the mutex, dereference the thread
    // and return failure.
    //

    if (ClientThread->LpcReplyMessageId != CapturedMessage.MessageId) {
        Status = STATUS_REPLY_MESSAGE_MISMATCH;
        }
    else {
        Status = STATUS_INVALID_PARAMETER;
        Msg = LpcpFindDataInfoMessage( PortObject,
                                       CapturedMessage.MessageId,
                                       CapturedMessage.CallbackId
                                     );
        if (Msg != NULL) {
            DataInfo = (PPORT_DATA_INFORMATION)((PUCHAR)&Msg->Request +
                                                Msg->Request.u2.s2.DataInfoOffset
                                               );
            if (DataInfo->CountDataEntries > DataEntryIndex) {
                DataEntry = &DataInfo->DataEntries[ DataEntryIndex ];
                CapturedDataEntry = *DataEntry;
                if (CapturedDataEntry.Size >= BufferSize) {
                    Status = STATUS_SUCCESS;
                    }
                }
            }
        }

    if (!NT_SUCCESS( Status )) {
        ExReleaseFastMutex( &LpcpLock );
        ObDereferenceObject( ClientThread );
        ObDereferenceObject( PortObject );
        return( Status );
        }

    //
    // Release the mutex that guards the LpcReplyMessage field
    //

    ExReleaseFastMutex( &LpcpLock );


    //
    // Copy the message data
    //

    if (WriteToMessageData) {
        Status = MmCopyVirtualMemory( PsGetCurrentProcess(),
                                      Buffer,
                                      THREAD_TO_PROCESS( ClientThread ),
                                      CapturedDataEntry.Base,
                                      BufferSize,
                                      PreviousMode,
                                      &BytesCopied
                                    );
        }
    else {
        Status = MmCopyVirtualMemory( THREAD_TO_PROCESS( ClientThread ),
                                      CapturedDataEntry.Base,
                                      PsGetCurrentProcess(),
                                      Buffer,
                                      BufferSize,
                                      PreviousMode,
                                      &BytesCopied
                                    );
        }

    if (ARGUMENT_PRESENT( NumberOfBytesCopied )) {
        try {
            *NumberOfBytesCopied = BytesCopied;
            }
        except( EXCEPTION_EXECUTE_HANDLER ) {
            NOTHING;
            }
        }

    //
    // Dereference client thread and return the system service status.
    //

    ObDereferenceObject( ClientThread );
    ObDereferenceObject( PortObject );
    return( Status );
}
